{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Support Vector Machines\n",
    "\n",
    "In this notebook, we will build a Support Vector Machine (SVM) that will find the optimal hyperplane that maximizes the margin between two toy data classes using gradient descent. An SVM is supervised machine learning algorithm which can be used for both classification or regression problems. But it's usually used for classification. Given 2 or more labeled classes of data, it acts as a discriminative classifier, formally defined by an optimal hyperplane that seperates all the classes. New examples that are then mapped into that same space can then be categorized based on on which side of the gap they fall.\n",
    "\n",
    "Support vectors are the data points nearest to the hyperplane, the points of a data set that, if removed, would alter the position of the dividing hyperplane. Because of this, they can be considered the critical elements of a data set, they are what help us build our SVM.\n",
    "\n",
    "A hyperplane is a linear decision surface that splits the space into two parts; a hyperplane is a binary classifier. Geometry tells us that a hyperplane is a subspace of one dimension less than its ambient space. For instance, a hyperplane of an n-dimensional space is a flat subset with dimension n − 1. By its nature, it separates the space into two half spaces."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Generating Toy Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.collections.PathCollection at 0x7f665cfbc280>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# %matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "from sklearn.datasets import make_blobs\n",
    "\n",
    "# Generate toy data that has two distinct classes and a huge gap between them\n",
    "X, Y = make_blobs(n_samples=500, centers=2, random_state=0, cluster_std=0.4)  # X - features, Y - labels\n",
    "# Plot the toy data\n",
    "plt.scatter(x=X[:, 0], y=X[:, 1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Creating our Support Vector Machine\n",
    "\n",
    "We will be using PyTorch to create our SVM."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.optim as optim\n",
    "from torch.autograd import Variable\n",
    "\n",
    "class SVM(nn.Module):\n",
    "    \"\"\"\n",
    "    Linear Support Vector Machine\n",
    "    -----------------------------\n",
    "    This SVM is a subclass of the PyTorch nn module that\n",
    "    implements the Linear  function. The  size  of  each \n",
    "    input sample is 2 and output sample  is 1.\n",
    "    \"\"\"\n",
    "    def __init__(self):\n",
    "        super().__init__()  # Call the init function of nn.Module\n",
    "        self.fully_connected = nn.Linear(2, 1)  # Implement the Linear function\n",
    "        \n",
    "    def forward(self, x):\n",
    "        fwd = self.fully_connected(x)  # Forward pass\n",
    "        return fwd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Our Support Vector Machine (SVM) is a subclass of the `nn.Module` class and to initialize our SVM, we call the base class' `init` function. Our `Forward` function applies a linear transformation to the incoming data: *y = Ax + b*."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Feature Scaling\n",
    "\n",
    "Feature scaling is a method used to standardize the range of independent variables or features of data. In data processing, it is also known as data normalization and is generally performed during the data preprocessing step. Standardizing the features so that they are centered around 0 with a standard deviation of 1 is not only important if we are comparing measurements that have different units, but it is also a general requirement for many machine learning algorithms.\n",
    "\n",
    "But why feature scaling?\n",
    "\n",
    "\n",
    "> The true reason behind scaling features in SVM is the fact, that this classifier is not affine transformation invariant. In other words, if you multiply one feature by a 1000 than a solution given by SVM will be completely different. It has nearly nothing to do with the underlying optimization techniques (although they are affected by these scales problems, they should still converge to global optimum).\n",
    "\n",
    "> Consider an example: you have man and a woman, encoded by their sex and height (two features). Let us assume a very simple case with such data:\n",
    "\n",
    "> 0-man, 1-woman\n",
    "\n",
    "> 1 150\n",
    "\n",
    "> 1 160\n",
    "\n",
    "> 1 170\n",
    "\n",
    "> 0 180\n",
    "\n",
    "> 0 190\n",
    "\n",
    "> 0 200\n",
    "\n",
    "> And let us do something silly. Train it to predict the sex of the person, so we are trying to learn f(x,y)=x (ignoring second parameter).\n",
    "\n",
    "> It is easy to see, that for such data largest margin classifier will \"cut\" the plane horizontally somewhere around height \"175\", so once we get new sample \"0 178\" (a woman of 178cm height) we get the classification that she is a man.\n",
    "\n",
    "> However, if we scale down everything to [0,1] we get sth like\n",
    "\n",
    "> 0 0.0\n",
    "\n",
    "> 0 0.2\n",
    "\n",
    "> 0 0.4\n",
    "\n",
    "> 1 0.6\n",
    "\n",
    "> 1 0.8\n",
    "\n",
    "> 1 1.0\n",
    "\n",
    "> and now largest margin classifier \"cuts\" the plane nearly vertically (as expected) and so given new sample \"0 178\" which is also scaled to around \"0 0.56\" we get that it is a woman (correct!)\n",
    "\n",
    "Source: *scaling?, W. (2018). Why feature scaling?. [online] Stackoverflow.com. Available at: https://stackoverflow.com/questions/26225344/why-feature-scaling?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa [Accessed 6 Apr. 2018].*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.collections.PathCollection at 0x7f6604025e50>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "data = X  # Before feature scaling\n",
    "X = (X - X.mean())/X.std()  # Feature scaling\n",
    "Y[Y == 0] = -1  # Replace zeros with -1\n",
    "plt.scatter(x=X[:, 0], y=X[:, 1])  # After feature scaling\n",
    "plt.scatter(x=data[:, 0], y=data[:, 1], c='r')  # Before feature scaling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training\n",
    "\n",
    "Now let's go ahead and train our SVM."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'torch' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[1;32m~\\AppData\\Local\\Temp/ipykernel_9708/3681494172.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m      3\u001b[0m \u001b[0mbatch_size\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;36m1\u001b[0m  \u001b[1;31m# Batch size\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      4\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 5\u001b[1;33m \u001b[0mX\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mFloatTensor\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mX\u001b[0m\u001b[1;33m)\u001b[0m  \u001b[1;31m# Convert X and Y to FloatTensors\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m      6\u001b[0m \u001b[0mY\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mFloatTensor\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mY\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      7\u001b[0m \u001b[0mN\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mY\u001b[0m\u001b[1;33m)\u001b[0m  \u001b[1;31m# Number of samples, 500\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mNameError\u001b[0m: name 'torch' is not defined"
     ]
    }
   ],
   "source": [
    "learning_rate = 0.1  # Learning rate\n",
    "epoch = 10  # Number of epochs\n",
    "batch_size = 1  # Batch size\n",
    "\n",
    "X = torch.FloatTensor(X)  # Convert X and Y to FloatTensors\n",
    "Y = torch.FloatTensor(Y)\n",
    "N = len(Y)  # Number of samples, 500\n",
    "\n",
    "model = SVM()  # Our model\n",
    "optimizer = optim.SGD(model.parameters(), lr=learning_rate)  # Our optimizer\n",
    "model.train()  # Our model, SVM is a subclass of the nn.Module, so it inherits the train method\n",
    "for epoch in range(epoch):\n",
    "    perm = torch.randperm(N)  # Generate a set of random numbers of length: sample size\n",
    "    sum_loss = 0  # Loss for each epoch\n",
    "        \n",
    "    for i in range(0, N, batch_size):\n",
    "        x = X[perm[i:i + batch_size]]  # Pick random samples by iterating over random permutation\n",
    "        y = Y[perm[i:i + batch_size]]  # Pick the correlating class\n",
    "        \n",
    "        x = Variable(x)  # Convert features and classes to variables\n",
    "        y = Variable(y)\n",
    "\n",
    "        optimizer.zero_grad()  # Manually zero the gradient buffers of the optimizer\n",
    "        output = model(x)  # Compute the output by doing a forward pass\n",
    "        \n",
    "        loss = torch.mean(torch.clamp(1 - output * y, min=0))  # hinge loss\n",
    "        loss.backward()  # Backpropagation\n",
    "        optimizer.step()  # Optimize and adjust weights\n",
    "\n",
    "        sum_loss += loss.data.cpu().item()  # Add the loss\n",
    "        \n",
    "    print(\"Epoch {}, Loss: {}\".format(epoch, sum_loss))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Results\n",
    "\n",
    "![Imgur](https://i.imgur.com/oSLROan.png)"
   ]
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
  },
  "kernelspec": {
   "display_name": "Python [conda root]",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
